package org.springframework.beans.factory.config;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.anno.Bean;
import org.springframework.beans.factory.anno.ComponentScan;
import org.springframework.beans.factory.anno.Import;
import org.springframework.beans.factory.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.util.ConfigurationClassUtils;
import org.springframework.beans.factory.util.StringUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.StandardMethodMetadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author:刘仁有
 * @desc:
 * @email:953506233@qq.com
 * @data:2019/12/16
 */
public class ConfigurationClassParser {
    private final BeanDefinitionRegistry registry;
    private ComponentScanAnnotationParser componentScanParser;
    private List<ConfigurationClass>configurationClazzs = new ArrayList<>();

    public ConfigurationClassParser(BeanDefinitionRegistry registry){
        this.registry = registry;
        componentScanParser = new ComponentScanAnnotationParser(registry);
    }


    public void parse(List<BeanDefinitionHolder> configCandidates) {
        for (BeanDefinitionHolder holder : configCandidates) {

            BeanDefinition bd = holder.getBeanDefinition();

            if (bd instanceof AnnotatedBeanDefinition) {
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }

            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).getBeanClass()!=null) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
        }
    }


    protected final void parse(Class<?> clazz, String beanName){
        processConfigurationClass(new ConfigurationClass(clazz, beanName));
    }

    protected final void parse(AnnotationMetadata metadata, String beanName){
        processConfigurationClass(new ConfigurationClass(metadata, beanName));
    }


    protected void processConfigurationClass(ConfigurationClass configClass){

        Map<String, Object> componentScans = configClass.getMetadata().getAnnotationAttributes(ComponentScan.class);
        String packageStr = (String) componentScans.get(ConfigurationClassUtils.CONFIGURATION_VALUE);
        Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(packageStr);

        for (BeanDefinitionHolder bdHolder:scannedBeanDefinitions) {
            BeanDefinition bd = bdHolder.getBeanDefinition();
            if(ConfigurationClassUtils.checkConfigurationClassCandidate(bd)&&!registry.containsBeanDefinition(bdHolder.getBeanName())){
                parse(bd.getBeanClass(),bdHolder.getBeanName());
            }
        }

        //处理import
        try {
            processImports(configClass,getImports(configClass));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        //@Bean 找到现在在解析的类所有的@Bean方法 然后addBeanMethod
        Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(configClass);
        for (MethodMetadata methodMetadata : beanMethods) {
            configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
        }

        configurationClazzs.add(configClass);
    }

    private Set<MethodMetadata> retrieveBeanMethodMetadata(ConfigurationClass configClass) {
        Set<MethodMetadata>beanMethods = new HashSet<>();
        Class clazz = configClass.getMetadata().getIntrospectedClass();
        if(clazz==null)return beanMethods;

        Method[]methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            if(method.isAnnotationPresent(Bean.class)){
                StandardMethodMetadata methodMetadata = new StandardMethodMetadata(method);
                beanMethods.add(methodMetadata);
            }
        }
        return beanMethods;
    }

    private void processImports(ConfigurationClass configClass, Class<?>[] imports) {
        if(null==imports)return;
        for (Class clazz:imports) {
            if(ImportBeanDefinitionRegistrar.class.isAssignableFrom(clazz)){
                ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(clazz, ImportBeanDefinitionRegistrar.class);
                configClass.addImportBeanDefinitionRegistrar(registrar,configClass.getMetadata());
            }else{
                AnnotatedBeanDefinition abd = new AnnotatedGenericBeanDefinition(clazz);
                String beanName = StringUtils.toLowerCaseFirstOne(clazz.getSimpleName());
                if(!registry.containsBeanDefinition(beanName)){
                    registry.registerBeanDefinition(beanName,abd);
                    processConfigurationClass(new ConfigurationClass(clazz, StringUtils.toLowerCaseFirstOne(clazz.getSimpleName())));
                }
            }
        }
    }

    //拿到import
    private Class<?> [] getImports(ConfigurationClass configClass) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Map<String, Object> imports = configClass.getMetadata().getAnnotationAttributes(Import.class);

        Set<Class<?>> classes = new HashSet<>();

        StandardAnnotationMetadata annotationMetadata = (StandardAnnotationMetadata) configClass.getMetadata();
        Annotation[]annotations =  annotationMetadata.getAnnotations();
        for (Annotation annotation:annotations) {
            StandardAnnotationMetadata sam = new StandardAnnotationMetadata(annotation.annotationType());
            Annotation[]annotations1 =  sam.getAnnotations();
            for (Annotation annotation1:annotations1) {
                if(annotation1.annotationType().equals(Import.class)){
                    Method method = annotation1.getClass().getDeclaredMethod("value",null);
                    Class<?> [] clazzss = (Class<?>[]) method.invoke(annotation1);
                    for (Class clazz:clazzss) {
                        classes.add(clazz);
                    }
                }
            }
        }

        if(null!=imports){
            Class<?> [] clazzs = (Class<?>[]) imports.get(ConfigurationClassUtils.CONFIGURATION_VALUE);
            for (Class clazz:clazzs) {
                classes.add(clazz);
            }
        }

        return classes.toArray(new Class[0]);
    }


    public List<ConfigurationClass> getConfigurationClazzs() {
        return configurationClazzs;
    }

}
